<?php
/*
 * description：
 * author：wh
 * email：
 * createTime：{2024/5/15} {21:40}
 */

namespace wanghua\general_utility_tools_php\api;


use wanghua\general_utility_tools_php\phpmailer\Exception;
use wanghua\general_utility_tools_php\tool\Tools;

/**
 * desc：接口文档构建器
 *
 * 使用步骤：
 * (new ApiDocument())->buildDoc();
 * Class ApiDocument
 */
class ApiDocument
{
    private $api_cache_arr = [];//缓存所有接口
    public $api_domain = 'http://127.0.0.1:8080/';//接口域名/ip
    /**
     * @var string 接口控制器命名空间
     */
    public $namespace = 'app\\api\\controller';
    /**
     * @var string 接口控制器基类，精确到类名，如：app\\common\\controller\\Api
     * 注意：如果同级目录存在多个基类，则设置直接基类，如果同级目录没有基类，则设置底层基类
     */
    public $extends_base_class = '';
    //控制器目录物理路径
    public $controllerDirectory = '';
    /**
     * @var string 接口文档保存路径
     */
    public $api_docs_save_dir = 'public/api_docs/';
    //设置过滤类
    private $filterClassArr = [];
    //设置过滤方法
    private $filterFunctionArr = [];

    public function __construct($api_domain='',$controllerDirectory='')
    {
        //默认，如果是tp6，那application就要改为app了，自行传参吧
        $this->controllerDirectory = Tools::get_root_path().'application/api/controller';
        if($api_domain){
            $this->api_domain = $api_domain;
        }
        if($controllerDirectory){
            $this->controllerDirectory = $controllerDirectory;
        }
    }

    /**
     * desc：设置过滤类（类名）
     * 过滤场景：
     * 1、基类
     * 2、测试类
     * 3、定时任务类
     * 4、其它非必要类
     * author：wh
     * @param array $filterClassArr
     */
    function setFilterClass(array $filterClassArr=[]){
        $this->filterClassArr = $filterClassArr;
    }
    function setFilterFunction(array $filterArr=[]){
        $this->filterFunctionArr = $filterArr;
    }
    /**
     * desc：构建接口文档，支持同步到在线文档
     * author：wh
     */
    function buildDoc(){
        if(empty($this->extends_base_class)){
            throw new Exception('请设置接口控制器基类，精确到类名，如：app\\common\\controller\\Api');
        }
        $out_path = Tools::get_root_path().$this->api_docs_save_dir;
        if(!file_exists($out_path)){
            mkdir($out_path,0777,true);
        }
        $outputFile = $out_path.'api_list.md';
        $controllerClasses = [];

        // 搜索控制器目录下的所有PHP文件
        foreach (glob($this->controllerDirectory . '/*.php') as $filename) {
            // 获取文件中的类名
            $class = basename($filename, '.php');
            if(in_array($class,$this->filterClassArr)){
                continue;
            }
            // 构建完整的命名空间类名
            $fullClassName = $this->namespace . '\\' . $class;
            foreach (explode(',',$this->extends_base_class) as $base_class){
                // 检查类是否有效并且是think\Controller的子类
                if (class_exists($fullClassName) && is_subclass_of($fullClassName, $base_class)) {
                    $controllerClasses[] = $fullClassName;
                }
            }
        }

        // 创建Markdown文件
        $file = fopen($outputFile, 'w') or die('无法创建文件');

        $head_text = <<<EOF
# API 文档 
## 接口列表
###### （ctrl+f 搜索）（如果更改了路由，请根据路由规则定位）
##### 请求域名：{$this->api_domain}
##### 请求方式：POST（默认）

EOF;
        // 写入Markdown文件头部
        fwrite($file, $head_text);

        foreach ($controllerClasses as $controllerClass) {
            $reflector = new \ReflectionClass($controllerClass);
            // 遍历控制器中的公共方法
            $methods = $reflector->getMethods(\ReflectionMethod::IS_PUBLIC);
            foreach ($methods as $method) {
                //过滤方法
                if(in_array($method->name, $this->filterFunctionArr)){
                    continue;
                }
                $exp_class = explode('\\',$controllerClass);
                //过滤类
                if(in_array($exp_class[count($exp_class)-1],$this->filterClassArr)){
                    continue;
                }
                $comments = $method->getDocComment();
                if ($comments) {
                    $this->processMethodComment($comments, $controllerClass, $method->name, $outputFile);
                }
            }
        }
        fclose($file);

        //缓存所有接口
        cache('api_doc_cache_arr',$this->api_cache_arr);
    }


    /**
     * desc：解析方法注释并写入Markdown文件
     * author：wh
     * @param $comments
     * @param $className
     * @param $methodName
     * @param $savepath
     */
    private function processMethodComment($comments, $className, $methodName, $savepath) {
        if($methodName == '__construct'){
            return '';
        }
        $api_url = "/api/{$className}/{$methodName}";
        $js_api_func_name = "api_{$className}_{$methodName}";
        $str = <<<EOF

***
```
    
EOF;

        $comments_str = mb_substr($comments,0,-2);
        $class_arr = explode('\\',$className);
        $className = $class_arr[count($class_arr)-1];
        $className = $this->camelCaseToUnderscore($className);


        $api_name = "api/{$className}/{$methodName}";
        $doc_txt = <<<EOF
     * $api_name
     */
```
EOF;
        $doc_txt= $str.$comments_str.$doc_txt."    \r\n";
        $this->api_cache_arr[] = ['api_name'=>$api_name,'doc_txt'=>$doc_txt];
        file_put_contents($savepath,$doc_txt, FILE_APPEND);
    }

    /**
     * desc：驼峰转下划线
     * author：wh
     * @param $string
     * @return string
     */
    private function camelCaseToUnderscore($string) {
        $str = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $string));
        return strpos($str,'_')===0?substr($str,1):$str;
    }

    function buildApiDocHtml(){
        $api_doc_cache_arr = cache('api_doc_cache_arr');
        $htm_str = "";
        $script_str = "";
        foreach ($api_doc_cache_arr as $item){
            $api_name = $item['api_name'];
            $doc_txt = $item['doc_txt'];

            $function_name = str_replace('/','_',$api_name);
            $htm_str .= <<<EOF
<div id="{$function_name}">
    <div class="markdown_content">{$doc_txt}</div>
    <div>
        按需填写其它接口参数：
        <textarea name="" id="{$function_name}_textarea" cols="100" rows="3">/{$api_name}</textarea>
        <a href='JavaScript:;' onclick="DocObject.{$function_name}()">测试</a>
    </div>
    <div class="{$function_name}_response_result"></div>
    
</div>
EOF;
            $script_str.=<<<EOF
        {$function_name}(){
            let url = $('#{$function_name}_textarea').val();
            $.post(url,{},function(res) {
                $('.{$function_name}_response_result').html(JSON.stringify(res, null, "\\t"));
                $('.{$function_name}_response_result').attr('style','color:green');
            },'json');
        },
EOF;


        }
        $html = <<<EOF
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>接口文档</title>
</head>
<body>
    <div>
        {$htm_str}
    </div>
</body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="/static/common/js/marked.min.js"></script>
<script>

    $(function() {
        //加载markdown
        DocObject.markdown_content();
    });
    
    let DocObject = {
         markdown_content(){
            $('.markdown_content').each(function(k,ele) {
                $(ele).html(marked.parse($(ele).html()));
            });
        },
        $script_str
    }
   
</script>
</html>
EOF;
        return $html;
    }
}